﻿using PSL.Collection;
using PSL.ST;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace PSL.Lexer
{
    public class Parser
    {
        #region Private Member
        private SyntaxTree syntaxTree;
        #endregion

        #region Constructor
        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="Tokenizer">Tokenizer with extracted tokens</param> 
        public Parser()
        {
            syntaxTree = new SyntaxTree();
        }
        #endregion

        #region Private Methods

        #endregion

        #region Public Methods

        #region [Parse Script]
        /// <summary>
        /// Parse Script
        /// </summary>
        /// <param name="Script"></param>
        /// <returns>Instance of an SyntaxTree which is ready for execution</returns>
        internal void Parse(Dequeue<string> Tokens, TreeNode ParentTreeNode = null)
        {
            // Set the parent-Tree-Node
            TreeNode parentTreeNode = ParentTreeNode == null ? syntaxTree.RootNode : ParentTreeNode;
            TreeNode currentNode = parentTreeNode;

            while (Tokens.Count > 0)
            {
                string token = Tokens.PopFirst();

                if (IsFunctionCall(token, Tokens))
                {
                    currentNode = ParseFunctionCall(token, Tokens, parentTreeNode);
                }
                else if (IsVariable(token))
                {
                    // Crate variable-Node
                    VariableNode varNode = currentNode.CreateChildNode<VariableNode>();
                    varNode.VariableName = token;
                    currentNode = varNode;

                    do
                    {
                        token = Tokens.PopFirst();
                    } while (token == Environment.NewLine);

                    // Token must be =
                    if (token == "=")
                    {
                        varNode.AccessType = VariableAccessType.Set;
                        
                        // Get all until semicolon
                        Dequeue<string> elements = TokenizerHelper.GetTokensUntilChar(Tokens);
                        ParseValue(elements, varNode);
                    }
                    else
                    {
                        throw new Exception("Expected assignment (=) behind: " + varNode.VariableName);
                    }
                }
                else if (token == ";")
                {
                    currentNode = parentTreeNode;
                }
                else if (token == Environment.NewLine)
                {

                }
                else
                {
                    throw new Exceptions.SyntaxError("Unexpected token: " + token);
                }
            }

            // Return Syntax-Tree for execution
        }
        #endregion

        #region [Function]
        /// <summary>
        /// Parse Function
        /// </summary>
        /// <param name="FunctionName"></param>
        /// <param name="Tokens"></param>
        /// <param name="ParentTreeNode"></param>
        /// <returns></returns>
        private CallFunctionNode ParseFunctionCall(string FunctionName, Dequeue<string> Tokens, TreeNode ParentTreeNode)
        {
            CallFunctionNode callFunctionNode = null;

            if (ParentTreeNode != null)
            {
                callFunctionNode = ParentTreeNode.CreateChildNode<CallFunctionNode>();
            }
            else
            {
                callFunctionNode = new CallFunctionNode(null);
            }

            callFunctionNode.FunctionName = FunctionName;

            Dequeue<string> innerFunctionTokens = GetInnerFunctionTokens(Tokens);
            int countTokens = innerFunctionTokens.Count;

            // Remove open and close-brackets
            // Remove open and close-Brackets
            innerFunctionTokens.PopLast();
            innerFunctionTokens.PopFirst();

            // When there are more tokens than the two brackets
            if (countTokens > 2)
            {
                // Get the single parameter as Deqeue<string>
                IList<Dequeue<string>> parameter = GetFunctionParameter(innerFunctionTokens);

                foreach (Dequeue<string> param in parameter)
                {
                    ParameterNode paramNode = callFunctionNode.CreateChildNode<ParameterNode>();
                    ParseValue(param, paramNode);
                }
            }

            return callFunctionNode;
        }

        /// <summary>
        /// Check, wether at the given index a function-call starts
        /// </summary>
        /// <param name="StartIndex">Start-Index</param>
        /// <returns>True if it is an Function-Call</returns>
        private bool IsFunctionCall(string CurrentToken, Dequeue<string> Tokens)
        {
            bool isFunctionCall = false;

            if (TokenizerHelper.ProofObjectName(CurrentToken, ProofObjectType.FunctionAndVariable, false))
            {
                if (Tokens.Count > 0)
                {
                    if (Tokens[0] == "(")
                    {
                        isFunctionCall = true;
                    }
                }
            }

            return isFunctionCall;
        }

        /// <summary>
        /// Get the content of function-call
        /// </summary>
        /// <param name="StartIndex">Start-Index</param>
        /// <returns></returns>
        private Dequeue<string> GetInnerFunctionTokens(Dequeue<string> Tokens)
        {
            Dequeue<string> returnList = new Dequeue<string>();

            int bracketCounting = 0;
            while (Tokens.Count > 0)
            {
                string token = Tokens.PopFirst();

                if (token == "(")
                {
                    bracketCounting++;
                }
                else if (token == ")")
                {
                    bracketCounting--;
                }

                returnList.PushBack(token);

                // Remove added token

                if (bracketCounting == 0)
                {
                    break;
                }
            }

            return returnList;
        }

        /// <summary>
        /// Extract all parameter
        /// </summary>
        /// <param name="InnerFunctionList">List which came from GetInenrFunctionTokens</param>
        /// <returns>List of Tokens with the parameter</returns>
        private IList<Dequeue<string>> GetFunctionParameter(Dequeue<string> InnerFunctionList)
        {
            IList<Dequeue<string>> returnValue = new List<Dequeue<string>>();

            Dequeue<string> currentQueue = new Dequeue<string>();
            returnValue.Add(currentQueue);

            foreach (string token in InnerFunctionList)
            {
                if (token == ",")
                {
                    currentQueue = new Dequeue<string>();
                    returnValue.Add(currentQueue);
                }
                else
                {
                    currentQueue.PushBack(token);
                }
            }

            return returnValue;
        }
        #endregion

        #region [Variables]
        /// <summary>
        /// Returns true, the tokens are variable
        /// </summary>
        /// <param name="CurrentToken"></param>
        /// <param name="Tokens"></param>
        /// <returns></returns>
        private bool IsVariable(string CurrentToken)
        {
            bool isVariable = false;

            if (TokenizerHelper.ProofObjectName(CurrentToken, ProofObjectType.FunctionAndVariable, false))
            {
                isVariable = true;
            }

            return isVariable;
        }
        #endregion

        #region [Value]
        /// <summary>
        /// Return the parsing-result of the case the token is a value-token
        /// </summary>
        /// <param name="Token"></param>
        /// <returns></returns>
        public GetValueTokenResult GetValueToken(string Token)
        {
            GetValueTokenResult returnValue = new GetValueTokenResult();
            returnValue.IsValueToken = false;

            if (Token.StartsWith("\"") && Token.EndsWith("\"") && Token.Length > 1)
            {
                returnValue.IsValueToken = true;
                returnValue.Value = Token.Substring(1, Token.Length - 2);
            }
            else if (Token == "true")
            {
                returnValue.Value = true;
                returnValue.IsValueToken = true;
            }
            else if (Token == "false")
            {
                returnValue.Value = false;
                returnValue.IsValueToken = true;
            }
            else if (Token.Contains("."))
            {
                double dummyOut;
                if (double.TryParse(Token, out dummyOut))
                {
                    returnValue.Value = dummyOut;
                    returnValue.IsValueToken = true;
                }
                else
                {
                    returnValue.IsValueToken = false;
                }
            }
            else
            {
                int dummyOut;
                if (int.TryParse(Token, out dummyOut))
                {
                    returnValue.Value = dummyOut;
                    returnValue.IsValueToken = true;
                }
                else
                {
                    returnValue.IsValueToken = false;
                }
            }

            return returnValue;
        }
        #endregion

        #region [Commands]
        /// <summary>
        /// Check wether the given token is a command
        /// </summary>
        /// <param name="Token">Token</param>
        /// <returns>True if the token is command</returns>
        public bool IsCommand(string Token)
        {
            return ParserConstants.CommandList.Contains(Token);
        }
        #endregion

        #region [Parse Value]
        /// <summary>
        /// Parse value, like "Hello World"; 1*2; 4*(3+2)*{GetValue}
        /// </summary>
        /// <param name="Line"></param>
        /// <param name="ParentNode"></param>
        /// <returns></returns>
        public void ParseValue(Dequeue<string> Tokens, TreeNode ParentNode)
        {
            // Trim every-thing from the Line-Parameter until the following format appears: " -CHAR"
            List<TreeNode> tokens = new List<TreeNode>();

            int currentDepth = 0;
            int maxDepth = 0;

            while (Tokens.Count > 0)
            {
                string token = Tokens.PopFirst();
                string paramValue = "";

                GetValueTokenResult getValueToken = GetValueToken(token);

                if (getValueToken.IsValueToken)
                {
                    tokens.Add(new ValueNode(null) { Value = getValueToken.Value });
                }
                else if (IsFunctionCall(token, Tokens))
                {
                    tokens.Add(ParseFunctionCall(token, Tokens, null));
                }
                else if (IsVariable(token))
                {
                    tokens.Add(new VariableNode(null) { VariableName = token, AccessType = VariableAccessType.Get });
                }
                else if (token == "(")
                {
                    tokens.Add(new BracketNode(null) { Type = BracketType.Open });
                }
                else if (token == ")")
                {
                    tokens.Add(new BracketNode(null) { Type = BracketType.Close });
                }
                else if (token == "+")
                {
                    tokens.Add(new OperatorNode(null) { OperatorType = OperatorType.Add, Association = OperatorAssociation.Left });
                }
                else if (token == "-")
                {
                    tokens.Add(new OperatorNode(null) { OperatorType = OperatorType.Sub, Association = OperatorAssociation.Left });
                }
                else if (token == "*")
                {
                    tokens.Add(new OperatorNode(null) { OperatorType = OperatorType.Mul, Association = OperatorAssociation.Left });
                }
                else if (token == "/")
                {
                    tokens.Add(new OperatorNode(null) { OperatorType = OperatorType.Div, Association = OperatorAssociation.Left });
                }
                else if (token == "^")
                {
                    tokens.Add(new OperatorNode(null) { OperatorType = OperatorType.Pow, Association = OperatorAssociation.Left });
                }
                else
                {
                    throw new Exceptions.SyntaxError("Unexpected token: " + token);
                }
            }

            // Build the SyntaxTree nodes from the tokens-list
            int openTerms = tokens.OfType<BracketNode>().Where(Item => Item.Type == BracketType.Open).ToList().Count;
            int closeTerms = tokens.OfType<BracketNode>().Where(Item => Item.Type == BracketType.Close).ToList().Count;

            if (openTerms > closeTerms)
            {
                throw new Exceptions.SyntaxError("Syntax-Error: near line: . Missing )");
            }
            else if (openTerms < closeTerms)
            {
                throw new Exceptions.SyntaxError("Syntax-Error: near line: . Missing (");
            }

            // Reorder tokens with the shunting-yard algorithm
            ShuntingYard yard = new ShuntingYard(tokens);
            List<TreeNode> postFixTokenized = yard.Execute().ToList();

            // Parse Post-Fix to Binary tree of Tokenized-Elements
            Stack<TreeNode> tempStack = new Stack<TreeNode>();
            TreeNode lastNode = null;

            foreach (TreeNode orderedToken in postFixTokenized)
            {
                if (!ShuntingYard.TokenIsOperator(orderedToken))
                {
                    tempStack.Push(orderedToken);
                }
                else
                {
                    var firstNode = tempStack.Pop();
                    orderedToken.Children.Add(firstNode);
                    firstNode.ParentNode = orderedToken;

                    var secondNode = tempStack.Pop();
                    orderedToken.Children.Add(secondNode);
                    secondNode.ParentNode = orderedToken;

                    tempStack.Push(orderedToken);
                }

                lastNode = orderedToken;
            }

            ParentNode.Children.Add(lastNode);
            lastNode.ParentNode = ParentNode;
        }
        #endregion

        #endregion

        #region Public Member
        /// <summary>
        /// Parsed syntax tree
        /// </summary>
        public SyntaxTree SyntaxTree
        {
            get { return syntaxTree; }
        }
        #endregion
    }
}
